/*
 * SRMI (an easy-to-use zero-code remoting library for Java)
 * Copyright (C) 2007  Mohammad Nabil Hussein
 * mohammad (dot) nabil (dot) h (at) gmail (dot) com
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

package SRMI.RemotingServices;

import SRMI.RemotingServices.ClassGeneration.Client.RmiStubAdapterGenerator;
import SRMI.RemotingServices.ClassGeneration.RemoteInterfaceGenerator;
import SRMI.RemotingServices.ClassGeneration.Server.RmiServiceWrapperGenerator;
import SRMI.SRMIException;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.PrintWriter;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.UnknownHostException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Scanner;
import java.util.UUID;
import java.util.logging.Level;
import org.apache.log4j.Logger;

/**
 * Common utilities used by many other classes.
 * @author meemo
 */
public class CommonUtils
{
    public final static int registryPortNumber = 1111;
    protected final static String CACHED_IP_FILE = "cached-external-ip";
    
    private static final Logger l = Logger.getLogger(CommonUtils.class);
    
    /**
     * Generate the class files of the classes needed to remote 
     * the specified classes. This can be done anytime, but this
     * method helps in streamlining the generation at the setup 
     * and caches the generated files instead of generating them
     * in the middle of program execution. The generated
     * classes is saved on the file system in srmi.basedir.
     * @param classes The list of all the classes to be prepared.
     */
    public static void PrepareClassesForRemoting(Class[] classes)
    {
        for(Class clazz : classes)
        {
            l.info("Batch preparing " + clazz.getName());
            try
            {
                new RemoteInterfaceGenerator(clazz).Prepare();
                new RmiServiceWrapperGenerator(clazz).Prepare();
                new RmiStubAdapterGenerator(clazz).Prepare();
            }
            catch(Exception ex)
            {
                l.error("Batch preparing " + clazz.getName() + " failed",ex);
            }
        }
    }
    
    /**
     * The external IP of localhost. In case there is multiple network
     * interfaces.
     */
    public static String localhostExternalIP = null;
    
    /**
     * This method automatically detects the list of
     * network interfaces. If there is only one, it is selected.
     * If there is more than one, it lets the user choose interactivly.
     * @return The detected (or user-choosen) external IP. That is is the one that is
     * going to be used by hosts wanting to communicate with us.
     * @throws SRMI.SRMIException
     */
    public static String detectExternalInterfaceIP() throws SRMIException
    {
        try
            {
            if(localhostExternalIP != null)
            {
                l.info("Reusing cached external IP " + localhostExternalIP);
                return localhostExternalIP;
            }

            ArrayList<String> externalInterfaces = new ArrayList<String>();

            // Detect the eth0 serverIP, not localLoop (lo) address
            for(Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces();
            e.hasMoreElements();)
            {
                NetworkInterface nInterface = e.nextElement();
                if(!nInterface.isLoopback())
                    for(InterfaceAddress addr : nInterface.getInterfaceAddresses())
                    {
                        InetAddress ipAddr = addr.getAddress();
                        if(ipAddr instanceof  Inet4Address) // Use only IPv4 addresses
                        {
                            localhostExternalIP = ipAddr.toString();
                            if(localhostExternalIP.startsWith("/"))
                                localhostExternalIP = localhostExternalIP.substring(1); // remover leading "/"
                            externalInterfaces.add(localhostExternalIP);
                        }
                    }
            }
            if(externalInterfaces.isEmpty())
            {
                try {
                    System.out.println("Couldn't detect non-local loop interface, using local loop");
                    localhostExternalIP = InetAddress.getLocalHost().toString();
                } catch (UnknownHostException ex) {
                    java.util.logging.Logger.getLogger(CommonUtils.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
            else
            {
                if(externalInterfaces.size() == 1)
                    localhostExternalIP = externalInterfaces.get(0);
                else
                {
                    localhostExternalIP = UserSelectIP(externalInterfaces);
                }
                System.out.println("External interface IP is " + localhostExternalIP);
            }

            return localhostExternalIP;
        }
        catch(Exception ex)
        {
            throw new SRMIException("Cannot detect external interface IP",ex);
        }
    }
    
    /**
     * Gets the rmi registry instance at the specified portNumber on localhost.
     * @param portNumber The port number where to look for rmi registry.
     * @return The rmi registry instance found. Can't be null.
     * @throws SRMI.SRMIException Thrown in case of RemoteException due to mismatched JDK version
     * or due to mismatched serialVersionUUID of the registry class. Or if the returned instance is 
     * null, meaning there is no rmi registry is found.
     */
    public static Registry getRmiRegistry(final int portNumber) throws SRMIException
    {
        Registry rmiRegistry = null;
        
        // cause exception if no registry exists so we create one instead
        try
        {
            rmiRegistry = LocateRegistry.getRegistry( portNumber );
            
            // test registry
            rmiRegistry.list();
            
            // if we reach this statement then the previous statement worked fine with no exceptions, thus an exiting registry is already there
            l.info("Found an existing registry");
        }
        catch (RemoteException e)
        {
            throw new SRMIException("Can't get RMI Registry (remoteException), version (JDK, or serialVersionUUID) issues ?",e);
        }
        if(rmiRegistry == null)
            throw new SRMIException("Can't get RMI Registry (null), version (JDK, or serialVersionUUID) issues ?");
        
        return rmiRegistry;
    }
    
    /**
     * Generated a new UUID. (calls UUID.randomUUID(), but that might be
     * changed in the future).
     * @param obj Unused
     * @return A new generated UUID.
     */
    public static UUID GetNewUUID(/*unused*/ Object obj)
    {
        return UUID.randomUUID();
    }
    
    /**
     * Interactively let the user select the desired IP for remoting.
     * It also caches it in a file.
     * @param externalInterfaces
     * @return
     */
    private static String UserSelectIP(ArrayList<String> externalInterfaces)
    {
        // we must cache it, cuz multiple roles need it
        // they also musy be run in the same working directory        
        File cached_ip_file = new File(CACHED_IP_FILE);
        if(cached_ip_file.exists())
        {
            try
            {
                return new BufferedReader(new FileReader(cached_ip_file)).readLine();
            } catch (Exception ex)
            {
                System.out.println("Can't read the IP cache file");
                ex.printStackTrace();
            }
        }
                
        System.out.println("Cannot auto-select external IP because there are many, please select the one of interest");
        int i = 0;
        for(String ip : externalInterfaces)
        {
            System.out.println(i++ + ") /" + ip);
        }
        
        boolean notDone = true;
        int input = 0;
        
        while(notDone)
        {
            try
            {
                System.out.println("Please enter the IP number:");
                Scanner s = new Scanner(System.in);
                input = s.nextInt();
                while( input < 0 && input >= externalInterfaces.size() )
                {
                    i = 0;
                    for(String ip : externalInterfaces)
                    {
                        System.out.println(i++ + " " + ip);
                    }
                    System.out.println("Out of range [0 ," + (externalInterfaces.size()-1) + "], Please enter again:");
                    input = s.nextInt();
                }
                notDone = false;
            }
            catch(Exception ex)
            {}
        }
        String ip = externalInterfaces.get(input);
        try
        {
            PrintWriter pw = new PrintWriter(cached_ip_file);
            pw.print(ip);
            pw.flush();
            pw.close();
        } catch (FileNotFoundException ex)
        {
            System.out.println("Can't write the IP cache file");
            ex.printStackTrace();
        }
        return ip;
    }
    
}
